package bancosys.tec.services.implementor.descriptor;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import jmine.tec.di.injector.DependencyInjector;
import jmine.tec.di.injector.Injector;
import jmine.tec.di.injector.impl.DependencyInjectorImpl;
import jmine.tec.di.injector.impl.FieldBasedInjector;
import jmine.tec.di.injector.impl.MethodBasedInjector;
import jmine.tec.di.metadata.DependencyMetaData;
import jmine.tec.utils.reflection.fast.FastMethodFactory;
import jmine.tec.utils.reflection.fast.FastMethodSingleArgument;
import bancosys.tec.services.implementor.annotations.visitor.DependencyMetadataParser;

/**
 * @author MarceloT
 */
public class DependencyDescriber {

    /**
     * Comparator que ordena {@link Injector} de acordo com os nomes das propriedades
     * 
     * @author takeshi
     */
    private static final class InjectorComparator implements Comparator<Injector> {

        /**
         * {@inheritDoc}
         * 
         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
         */
        public int compare(Injector o1, Injector o2) {
            return o1.getDependencyMetaData().getPropertyName().compareTo(o2.getDependencyMetaData().getPropertyName());
        }
    }

    private final DependencyMetadataParser dependencyMetadataParser;

    private final FastMethodFactory methodFactory;

    private final InjectorComparator injectorComparator = new InjectorComparator();

    /**
     * @param dependencyMetadataParser the {@link DependencyMetadataParser}
     * @param factory the {@link FastMethodFactory}
     */
    public DependencyDescriber(DependencyMetadataParser dependencyMetadataParser, FastMethodFactory factory) {
        this.dependencyMetadataParser = dependencyMetadataParser;
        this.methodFactory = factory;
    }

    /**
     * @param type o tipo
     * @return {@link DependencyInjector}
     */
    public DependencyInjector createDependencyInjectorFor(Class<?> type) {
        List<Injector> injectors = this.createInjectorsFor(type);
        return new DependencyInjectorImpl(injectors.toArray(new Injector[injectors.size()]));
    }

    /**
     * Cria uma colecao com os injectors de metodo para o tipo passado
     * 
     * @param type o tipo
     * @return {@link List}
     */
    public List<Injector> createInjectorsFor(Class<?> type) {
        Set<String> methodsVisited = new HashSet<String>();
        List<Injector> injectors = new ArrayList<Injector>();
        for (Class<?> selected : this.dependencyMetadataParser.getClassReader().selectTypes(type)) {
            for (Method method : this.dependencyMetadataParser.getClassReader().selectMethods(selected)) {
                if (methodsVisited.add(keyForMethod(method))) {
                    Collection<? extends DependencyMetaData> collection =
                            this.dependencyMetadataParser.getClassReader().visitMethodAnnotation(method, type);
                    if (!collection.isEmpty()) {
                        DependencyMetaData dep = collection.iterator().next();
                        Injector injector =
                                new MethodBasedInjector((FastMethodSingleArgument) this.methodFactory.getMethodAccessorFor(method), dep);
                        injectors.add(injector);
                    }
                }
            }
            for (Field f : this.dependencyMetadataParser.getClassReader().selectFields(type)) {
                Collection<? extends DependencyMetaData> collection =
                        this.dependencyMetadataParser.getClassReader().visitFieldAnnotations(selected, f);
                if (!collection.isEmpty()) {
                    DependencyMetaData dep = collection.iterator().next();
                    Injector injector = new FieldBasedInjector(f, dep);
                    injectors.add(injector);
                }

            }
        }
        if (injectors.isEmpty()) {
            return Collections.emptyList();
        } else {
            Collections.sort(injectors, injectorComparator);
        }
        return injectors;
    }

    /**
     * @param method {@link Method}
     * @return String
     */
    private String keyForMethod(Method method) {
        return method.getName() + "[" + Arrays.toString(method.getParameterTypes()) + "]";
    }

}
